package org.leolsean.util;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

@Slf4j
public class HttpClientUtil {

  /**
   * 连接池最大连接数
   */
  public static Integer MAX_CONNECTION = 32;
  /**
   * 单个路由默认最大连接数
   */
  public static Integer MAX_CONNPERROUTE = 16;
  /**
   * 使用连接池来管理连接,从连接池获取连接的超时时间 - 从连接池获取连接的timeout ms
   */
  public static Integer CONNECTION_REQUEST_TIMEOUT = 3 * 1000;
  /**
   * 连接超时,连接建立时间,三次握手完成时间 - 客户端和服务器建立连接的timeout ms
   */
  public static Integer CONNECTION_TIMEOUT = 5 * 1000;
  /**
   * 请求超时,数据传输过程中数据包之间间隔的最大时间 - 连接建立后，request没有回应的timeout ms
   */
  public static Integer SOCKET_TIMEOUT = 5 * 1000;

  public static CloseableHttpClient getHttpClient() {
    return HttpClientHolder.HTTP_CLIENT;
  }

  public static CloseableHttpAsyncClient getAsyncClient() {
    return HttpClientHolder.ASYNC_HTTP_CLIENT;
  }

  /**
   * 静态内部类单例模式
   */
  private static class HttpClientHolder {

    private static final CloseableHttpClient HTTP_CLIENT = HttpClientUtil.createHttpClient();
    //创建异步httpclient对象
    private static final CloseableHttpAsyncClient ASYNC_HTTP_CLIENT = HttpClientUtil
        .createAsyncHttpClient();
  }

  private static ConnectionKeepAliveStrategy initConnectionKeepAliveStrategy() {
    //DefaultConnectionKeepAliveStrategy 默认实现
    return (response, context) -> {
      final HeaderElementIterator it = new BasicHeaderElementIterator(
          response.headerIterator(HTTP.CONN_KEEP_ALIVE));
      while (it.hasNext()) {
        final HeaderElement he = it.nextElement();
        final String param = he.getName();
        final String value = he.getValue();
        if (value != null && param.equalsIgnoreCase("timeout")) {
          try {
            return Long.parseLong(value) * 1000;
          } catch (final NumberFormatException ignore) {
          }
        }
      }
      return 1;
    };
  }

  private static RequestConfig initRequestConfig() {
    return RequestConfig.custom()
        //客户端和服务器建立连接的timeout
        .setConnectTimeout(CONNECTION_TIMEOUT)
        //从连接池获取连接的timeout
        .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
        //连接建立后，request没有回应的timeout
        .setSocketTimeout(SOCKET_TIMEOUT).build();
  }


  /**
   * 初始化客户端
   */
  private static CloseableHttpClient createHttpClient() {
    return HttpClients.custom()
        .setMaxConnTotal(MAX_CONNECTION)
        .setMaxConnPerRoute(MAX_CONNPERROUTE)
        .setDefaultRequestConfig(initRequestConfig())
        //连接建立后，request没有回应的timeout
        .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(SOCKET_TIMEOUT).build())
        .setKeepAliveStrategy(initConnectionKeepAliveStrategy())
        .setConnectionManagerShared(true)
        .evictExpiredConnections()
        .build();
  }

  private static CloseableHttpAsyncClient createAsyncHttpClient() {
    RequestConfig requestConfig = initRequestConfig();

    //配置io线程
    IOReactorConfig ioReactorConfig = IOReactorConfig.custom().
        setIoThreadCount(Runtime.getRuntime().availableProcessors())
        .setSoKeepAlive(true)
        .build();
    //设置连接池大小
    ConnectingIOReactor ioReactor;
    try {
      ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
      PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(
          ioReactor);
      //最大连接数设置
      connManager.setMaxTotal(5);
      //per route最大连接数设置
      connManager.setDefaultMaxPerRoute(5);

      CloseableHttpAsyncClient client = HttpAsyncClients.custom()
          .setConnectionManager(connManager)
          .setDefaultRequestConfig(requestConfig)
          .build();
      client.start();
      return client;
    } catch (IOReactorException e) {
      log.error("初始化异步HttpClient异常!", e);
    }
    return null;
  }

  private static String buildUrl(String apiUrl, Map<String, String> querys, String encoding)
      throws UnsupportedEncodingException {
    StringBuilder sbUrl = new StringBuilder();
    sbUrl.append(apiUrl);
    if (null != querys) {
      StringBuilder sbQuery = new StringBuilder();
      for (Map.Entry<String, String> query : querys.entrySet()) {
        if (0 < sbQuery.length()) {
          sbQuery.append("&");
        }
        if (query.getKey() != null && !"".equals(query.getKey()) && query.getValue() != null && !""
            .equals(query.getValue())) {
          sbQuery.append(query.getValue());
        }
        if (query.getKey() != null && !"".equals(query.getKey())) {
          sbQuery.append(query.getKey());
          if (query.getValue() != null && !"".equals(query.getValue())) {
            sbQuery.append("=");
            sbQuery.append(URLEncoder.encode(query.getValue(), encoding));
          }
        }
      }
      if (0 < sbQuery.length()) {
        sbUrl.append("?").append(sbQuery);
      }
    }
    log.debug("Get请求地址:{}", sbUrl);
    return sbUrl.toString();
  }

  public static ResponseEntity sendGet(String apiUrl, Map<String, String> headers,
      Map<String, String> paramMap, String encoding) {
    ResponseEntity responseEntity = new ResponseEntity();
    String url = null;
    HttpGet request = null;
    try {
      url = buildUrl(apiUrl, paramMap, encoding);
      request = new HttpGet(url);
      if (headers != null) {
        for (Map.Entry<String, String> e : headers.entrySet()) {
          request.addHeader(e.getKey(), e.getValue());
        }
      }
      HttpResponse response = HttpClientUtil.getHttpClient().execute(request);
      int statusCode = response.getStatusLine().getStatusCode();
      if (HttpStatus.SC_OK != statusCode) {
        log.warn("http get 请求失败，错误码: {}, 信息: {}", statusCode,
            response.getStatusLine().getReasonPhrase());
      }
      responseEntity.setStatusCode(statusCode);
      responseEntity.setBody(EntityUtils.toString(response.getEntity(), encoding));
    } catch (IOException e) {
      log.error("请求异常! URL: {}, message: {}", url, e.getMessage());
    } finally {
      if (request != null) {
        request.releaseConnection();
      }
    }
    return responseEntity;
  }

  /**
   * http请求统一返回vo
   */
  @Data
  public static class ResponseEntity {

    private int statusCode;

    /**
     * 返回报文
     */
    private String body;

    public boolean isSuccess() {
      return HttpStatus.SC_OK == statusCode;
    }
  }
}
